# Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-2024)
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import annotations

import hashlib
import inspect
import pathlib
import tempfile
import textwrap
import traceback
from typing import Any, Callable, Sequence
from unittest.mock import MagicMock
from urllib import parse

from streamlit import source_util
from streamlit.proto.WidgetStates_pb2 import WidgetStates
from streamlit.runtime import Runtime
from streamlit.runtime.caching.storage.dummy_cache_storage import (
    MemoryCacheStorageManager,
)
from streamlit.runtime.media_file_manager import MediaFileManager
from streamlit.runtime.memory_media_file_storage import MemoryMediaFileStorage
from streamlit.runtime.secrets import Secrets
from streamlit.runtime.state.common import TESTING_KEY
from streamlit.runtime.state.safe_session_state import SafeSessionState
from streamlit.runtime.state.session_state import SessionState
from streamlit.testing.v1.element_tree import (
    Block,
    Button,
    Caption,
    ChatInput,
    ChatMessage,
    Checkbox,
    Code,
    ColorPicker,
    Column,
    Dataframe,
    DateInput,
    Divider,
    ElementList,
    ElementTree,
    Error,
    Exception,
    Expander,
    Header,
    Info,
    Json,
    Latex,
    Markdown,
    Metric,
    Multiselect,
    Node,
    NumberInput,
    Radio,
    Selectbox,
    SelectSlider,
    Slider,
    Status,
    Subheader,
    Success,
    Tab,
    Table,
    Text,
    TextArea,
    TextInput,
    TimeInput,
    Title,
    Toast,
    Toggle,
    Warning,
    WidgetList,
    repr_,
)
from streamlit.testing.v1.local_script_runner import LocalScriptRunner
from streamlit.testing.v1.util import patch_config_options
from streamlit.util import HASHLIB_KWARGS

TMP_DIR = tempfile.TemporaryDirectory()


class AppTest:
    """
    A simulated Streamlit app to check the correctness of displayed\
    elements and outputs.

    An instance of ``AppTest`` simulates a running Streamlit app. This class
    provides methods to set up, manipulate, and inspect the app contents via
    API instead of a browser UI. It can be used to write automated tests of an
    app in various scenarios. These can then be run using a tool like pytest.

    ``AppTest`` can be initialized by one of three class methods:

    * |st.testing.v1.AppTest.from_file|_ (recommended)
    * |st.testing.v1.AppTest.from_string|_
    * |st.testing.v1.AppTest.from_function|_

    Once initialized, Session State and widget values can be updated and the
    script can be run. Unlike an actual live-running Streamlit app, you need to
    call ``AppTest.run()`` explicitly to re-run the app after changing a widget
    value.

    ``AppTest`` enables developers to build tests on their app as-is, in the
    familiar python test format, without major refactoring or abstracting out
    logic to be tested separately from the UI. Tests can run quickly with very
    low overhead. A typical pattern is to build a suite of tests for an app
    that ensure consistent functionality as the app evolves, and run the tests
    locally and/or in a CI environment like Github Actions.

    .. note::
        ``AppTest`` only supports testing a single page of an app per
        instance. For multipage apps, each page will need to be tested
        separately. No methods exist to programatically switch pages within
        ``AppTest``.

    .. |st.testing.v1.AppTest.from_file| replace:: ``st.testing.v1.AppTest.from_file``
    .. _st.testing.v1.AppTest.from_file: #apptestfrom_file
    .. |st.testing.v1.AppTest.from_string| replace:: ``st.testing.v1.AppTest.from_string``
    .. _st.testing.v1.AppTest.from_string: #apptestfrom_string
    .. |st.testing.v1.AppTest.from_function| replace:: ``st.testing.v1.AppTest.from_function``
    .. _st.testing.v1.AppTest.from_function: #apptestfrom_function

    Attributes
    ----------
    secrets: dict[str, Any]
        Dictionary of secrets to be used the simulated app. Use dict-like
        syntax to set secret values for the simulated app.

    session_state: SafeSessionState
        Session State for the simulated app. SafeSessionState object supports
        read and write operations as usual for Streamlit apps.

    query_params: dict[str, Any]
        Dictionary of query parameters to be used by the simluated app. Use
        dict-like syntax to set ``query_params`` values for the simulated app.
    """

    def __init__(
        self,
        script_path: str,
        *,
        default_timeout: float,
        args=None,
        kwargs=None,
    ):
        self._script_path = script_path
        self.default_timeout = default_timeout
        session_state = SessionState()
        session_state[TESTING_KEY] = {}
        self.session_state = SafeSessionState(session_state, lambda: None)
        self.query_params: dict[str, Any] = {}
        self.secrets: dict[str, Any] = {}
        self.args = args
        self.kwargs = kwargs

        tree = ElementTree()
        tree._runner = self
        self._tree = tree

    @classmethod
    def from_string(cls, script: str, *, default_timeout: float = 3) -> AppTest:
        """
        Create an instance of ``AppTest`` to simulate an app page defined\
        within a string.

        This is useful for testing short scripts that fit comfortably as an
        inline string in the test itself, without having to create a separate
        file for it. The script must be executable on its own and so must
        contain all necessary imports.

        Parameters
        ----------
        script: str
            The string contents of the script to be run.

        default_timeout: float
            Default time in seconds before a script run is timed out. Can be
            overridden for individual ``.run()`` calls.

        Returns
        -------
        AppTest
            A simulated Streamlit app for testing. The simulated app can be
            executed via ``.run()``.

        """
        return cls._from_string(script, default_timeout=default_timeout)

    @classmethod
    def _from_string(
        cls, script: str, *, default_timeout: float = 3, args=None, kwargs=None
    ) -> AppTest:
        hasher = hashlib.md5(bytes(script, "utf-8"), **HASHLIB_KWARGS)
        script_name = hasher.hexdigest()

        path = pathlib.Path(TMP_DIR.name, script_name)
        aligned_script = textwrap.dedent(script)
        path.write_text(aligned_script)
        return AppTest(
            str(path), default_timeout=default_timeout, args=args, kwargs=kwargs
        )

    @classmethod
    def from_function(
        cls,
        script: Callable[..., Any],
        *,
        default_timeout: float = 3,
        args=None,
        kwargs=None,
    ) -> AppTest:
        """
        Create an instance of ``AppTest`` to simulate an app page defined\
        within a function.

        This is similar to ``AppTest.from_string()``, but more convenient to
        write with IDE assistance. The script must be executable on its own and
        so must contain all necessary imports.

        Parameters
        ----------
        script: Callable
            A function whose body will be used as a script. Must be runnable
            in isolation, so it must include any necessary imports.

        default_timeout: float
            Default time in seconds before a script run is timed out. Can be
            overridden for individual ``.run()`` calls.

        args: tuple
            An optional tuple of args to pass to the script function.

        kwargs: dict
            An optional dict of kwargs to pass to the script function.

        Returns
        -------
        AppTest
            A simulated Streamlit app for testing. The simulated app can be
            executed via ``.run()``.

        """
        source_lines, _ = inspect.getsourcelines(script)
        source = textwrap.dedent("".join(source_lines))
        module = source + f"\n{script.__name__}(*__args, **__kwargs)"
        return cls._from_string(
            module, default_timeout=default_timeout, args=args, kwargs=kwargs
        )

    @classmethod
    def from_file(cls, script_path: str, *, default_timeout: float = 3) -> AppTest:
        """
        Create an instance of ``AppTest`` to simulate an app page defined\
        within a file.

        This option is most convenient for CI workflows and testing of
        published apps. The script must be executable on its own and so must
        contain all necessary imports.

        Parameters
        ----------
        script_path: str
            Path to a script file. The path should be absolute or relative to
            the file calling ``.from_file``.

        default_timeout: float
            Default time in seconds before a script run is timed out. Can be
            overridden for individual ``.run()`` calls.

        Returns
        -------
        AppTest
            A simulated Streamlit app for testing. The simulated app can be
            executed via ``.run()``.

        """
        if pathlib.Path.is_file(pathlib.Path(script_path)):
            path = script_path
        else:
            # TODO: Make this not super fragile
            # Attempt to find the test file calling this method, so the
            # path can be relative to there.
            stack = traceback.StackSummary.extract(traceback.walk_stack(None))
            filepath = pathlib.Path(stack[1].filename)
            path = str(filepath.parent / script_path)
        return AppTest(path, default_timeout=default_timeout)

    def _run(
        self,
        widget_state: WidgetStates | None = None,
        timeout: float | None = None,
    ) -> AppTest:
        """Run the script, and parse the output messages for querying
        and interaction.

        Timeout is in seconds, or None to use the default timeout of the runner.
        """
        # Have to import the streamlit module itself so replacing st.secrets
        # is visible to other modules.
        import streamlit as st

        if timeout is None:
            timeout = self.default_timeout

        # setup
        mock_runtime = MagicMock(spec=Runtime)
        mock_runtime.media_file_mgr = MediaFileManager(
            MemoryMediaFileStorage("/mock/media")
        )
        mock_runtime.cache_storage_manager = MemoryCacheStorageManager()
        Runtime._instance = mock_runtime
        with source_util._pages_cache_lock:
            saved_cached_pages = source_util._cached_pages
            source_util._cached_pages = None

        saved_secrets: Secrets = st.secrets
        # Only modify global secrets stuff if we have been given secrets
        if self.secrets:
            new_secrets = Secrets([])
            new_secrets._secrets = self.secrets
            st.secrets = new_secrets

        script_runner = LocalScriptRunner(
            self._script_path, self.session_state, args=self.args, kwargs=self.kwargs
        )
        with patch_config_options({"global.appTest": True}):
            self._tree = script_runner.run(widget_state, self.query_params, timeout)
            self._tree._runner = self
        # Last event is SHUTDOWN, so the corresponding data includes query string
        query_string = script_runner.event_data[-1]["client_state"].query_string
        self.query_params = parse.parse_qs(query_string)

        # teardown
        with source_util._pages_cache_lock:
            source_util._cached_pages = saved_cached_pages

        if self.secrets:
            if st.secrets._secrets is not None:
                self.secrets = dict(st.secrets._secrets)
            st.secrets = saved_secrets
        Runtime._instance = None

        return self

    def run(self, *, timeout: float | None = None) -> AppTest:
        """Run the script from the current state.

        This is equivalent to manually rerunning the app or the rerun that
        occurs upon user interaction. ``AppTest.run()`` must be manually called
        after updating a widget value as script reruns do not occur
        automatically as they do for live-running Streamlit apps.

        Parameters
        ----------
        timeout
            The maximum number of seconds to run the script. None means
            use the default timeout set for the instance of ``AppTest``.

        Returns
        -------
        AppTest
            self
        """
        return self._tree.run(timeout=timeout)

    @property
    def main(self) -> Block:
        """Sequence of elements within the main body of the app.

        Returns
        -------
        Block
            A container of elements. Block can be queried for elements in the
            same manner as ``AppTest``. For example, ``Block.checkbox`` will
            return all ``st.checkbox`` within the associated container.
        """
        return self._tree.main

    @property
    def sidebar(self) -> Block:
        """Sequence of all elements within ``st.sidebar``.

        Returns
        -------
        Block
            A container of elements. Block can be queried for elements in the
            same manner as ``AppTest``. For example, ``Block.checkbox`` will
            return all ``st.checkbox`` within the associated container.
        """
        return self._tree.sidebar

    @property
    def button(self) -> WidgetList[Button]:
        """Sequence of all ``st.button`` and ``st.form_submit_button`` widgets.

        Returns
        -------
        WidgetList of Button
            Sequence of all ``st.button`` and ``st.form_submit_button``
            widgets. Individual widgets can be accessed from a WidgetList by
            index (order on the page) or key. For example, ``at.button[0]`` for
            the first widget or ``at.button(key="my_key")`` for a widget with a
            given key.
        """
        return self._tree.button

    @property
    def caption(self) -> ElementList[Caption]:
        """Sequence of all ``st.caption`` elements.

        Returns
        -------
        ElementList of Caption
            Sequence of all ``st.caption`` elements. Individual elements can be
            accessed from an ElementList by index (order on the page). For
            example, ``at.caption[0]`` for the first element. Caption is an
            extension of the Element class.
        """
        return self._tree.caption

    @property
    def chat_input(self) -> WidgetList[ChatInput]:
        """Sequence of all ``st.chat_input`` widgets.

        Returns
        -------
        WidgetList of ChatInput
            Sequence of all ``st.chat_input`` widgets. Individual widgets can
            be accessed from a WidgetList by index (order on the page) or key.
            For example, ``at.chat_input[0]`` for the first widget or
            ``at.chat_input(key="my_key")`` for a widget with a given key.
        """
        return self._tree.chat_input

    @property
    def chat_message(self) -> Sequence[ChatMessage]:
        """Sequence of all ``st.chat_message`` elements.

        Returns
        -------
        Sequence of ChatMessage
            Sequence of all ``st.chat_message`` elements. Individual elements can be
            accessed from an ElementList by index (order on the page). For
            example, ``at.chat_message[0]`` for the first element.  ChatMessage
            is an extension of the Block class.
        """
        return self._tree.chat_message

    @property
    def checkbox(self) -> WidgetList[Checkbox]:
        """Sequence of all ``st.checkbox`` widgets.

        Returns
        -------
        WidgetList of Checkbox
            Sequence of all ``st.checkbox`` widgets. Individual widgets can
            be accessed from a WidgetList by index (order on the page) or key.
            For example, ``at.checkbox[0]`` for the first widget or
            ``at.checkbox(key="my_key")`` for a widget with a given key.
        """
        return self._tree.checkbox

    @property
    def code(self) -> ElementList[Code]:
        """Sequence of all ``st.code`` elements.

        Returns
        -------
        ElementList of Code
            Sequence of all ``st.code`` elements. Individual elements can be
            accessed from an ElementList by index (order on the page). For
            example, ``at.code[0]`` for the first element. Code is an
            extension of the Element class.
        """
        return self._tree.code

    @property
    def color_picker(self) -> WidgetList[ColorPicker]:
        """Sequence of all ``st.color_picker`` widgets.

        Returns
        -------
        WidgetList of ColorPicker
            Sequence of all ``st.color_picker`` widgets. Individual widgets can
            be accessed from a WidgetList by index (order on the page) or key.
            For example, ``at.color_picker[0]`` for the first widget or
            ``at.color_picker(key="my_key")`` for a widget with a given key.
        """
        return self._tree.color_picker

    @property
    def columns(self) -> Sequence[Column]:
        """Sequence of all columns within ``st.columns`` elements.

        Each column within a single ``st.columns`` will be returned as a
        separate Column in the Sequence.

        Returns
        -------
        Sequence of Column
            Sequence of all columns within ``st.columns`` elements. Individual
            columns can be accessed from an ElementList by index (order on the
            page). For example, ``at.columns[0]`` for the first column. Column
            is an extension of the Block class.
        """
        return self._tree.columns

    @property
    def dataframe(self) -> ElementList[Dataframe]:
        """Sequence of all ``st.dataframe`` elements.

        Returns
        -------
        ElementList of Dataframe
            Sequence of all ``st.dataframe`` elements. Individual elements can be
            accessed from an ElementList by index (order on the page). For
            example, ``at.dataframe[0]`` for the first element. Dataframe is an
            extension of the Element class.
        """
        return self._tree.dataframe

    @property
    def date_input(self) -> WidgetList[DateInput]:
        """Sequence of all ``st.date_input`` widgets.

        Returns
        -------
        WidgetList of DateInput
            Sequence of all ``st.date_input`` widgets. Individual widgets can
            be accessed from a WidgetList by index (order on the page) or key.
            For example, ``at.date_input[0]`` for the first widget or
            ``at.date_input(key="my_key")`` for a widget with a given key.
        """
        return self._tree.date_input

    @property
    def divider(self) -> ElementList[Divider]:
        """Sequence of all ``st.divider`` elements.

        Returns
        -------
        ElementList of Divider
            Sequence of all ``st.divider`` elements. Individual elements can be
            accessed from an ElementList by index (order on the page). For
            example, ``at.divider[0]`` for the first element. Divider is an
            extension of the Element class.
        """
        return self._tree.divider

    @property
    def error(self) -> ElementList[Error]:
        """Sequence of all ``st.error`` elements.

        Returns
        -------
        ElementList of Error
            Sequence of all ``st.error`` elements. Individual elements can be
            accessed from an ElementList by index (order on the page). For
            example, ``at.error[0]`` for the first element. Error is an
            extension of the Element class.
        """
        return self._tree.error

    @property
    def exception(self) -> ElementList[Exception]:
        """Sequence of all ``st.exception`` elements.

        Returns
        -------
        ElementList of Exception
            Sequence of all ``st.exception`` elements. Individual elements can be
            accessed from an ElementList by index (order on the page). For
            example, ``at.exception[0]`` for the first element. Exception is an
            extension of the Element class.
        """
        return self._tree.exception

    @property
    def expander(self) -> Sequence[Expander]:
        """Sequence of all ``st.expander`` elements.

        Returns
        -------
        Sequence of Expandable
            Sequence of all ``st.expander`` elements. Individual elements can be
            accessed from a Sequence by index (order on the page). For
            example, ``at.expander[0]`` for the first element. Expandable is an
            extension of the Block class.
        """
        return self._tree.expander

    @property
    def header(self) -> ElementList[Header]:
        """Sequence of all ``st.header`` elements.

        Returns
        -------
        ElementList of Header
            Sequence of all ``st.header`` elements. Individual elements can be
            accessed from an ElementList by index (order on the page). For
            example, ``at.header[0]`` for the first element. Header is an
            extension of the Element class.
        """
        return self._tree.header

    @property
    def info(self) -> ElementList[Info]:
        """Sequence of all ``st.info`` elements.

        Returns
        -------
        ElementList of Info
            Sequence of all ``st.info`` elements. Individual elements can be
            accessed from an ElementList by index (order on the page). For
            example, ``at.info[0]`` for the first element. Info is an
            extension of the Element class.
        """
        return self._tree.info

    @property
    def json(self) -> ElementList[Json]:
        """Sequence of all ``st.json`` elements.

        Returns
        -------
        ElementList of Json
            Sequence of all ``st.json`` elements. Individual elements can be
            accessed from an ElementList by index (order on the page). For
            example, ``at.json[0]`` for the first element. Json is an
            extension of the Element class.
        """
        return self._tree.json

    @property
    def latex(self) -> ElementList[Latex]:
        """Sequence of all ``st.latex`` elements.

        Returns
        -------
        ElementList of Latex
            Sequence of all ``st.latex`` elements. Individual elements can be
            accessed from an ElementList by index (order on the page). For
            example, ``at.latex[0]`` for the first element. Latex is an
            extension of the Element class.
        """
        return self._tree.latex

    @property
    def markdown(self) -> ElementList[Markdown]:
        """Sequence of all ``st.markdown`` elements.

        Returns
        -------
        ElementList of Markdown
            Sequence of all ``st.markdown`` elements. Individual elements can be
            accessed from an ElementList by index (order on the page). For
            example, ``at.markdown[0]`` for the first element. Markdown is an
            extension of the Element class.
        """
        return self._tree.markdown

    @property
    def metric(self) -> ElementList[Metric]:
        """Sequence of all ``st.metric`` elements.

        Returns
        -------
        ElementList of Metric
            Sequence of all ``st.metric`` elements. Individual elements can be
            accessed from an ElementList by index (order on the page). For
            example, ``at.metric[0]`` for the first element. Metric is an
            extension of the Element class.
        """
        return self._tree.metric

    @property
    def multiselect(self) -> WidgetList[Multiselect[Any]]:
        """Sequence of all ``st.multiselect`` widgets.

        Returns
        -------
        WidgetList of Multiselect
            Sequence of all ``st.multiselect`` widgets. Individual widgets can
            be accessed from a WidgetList by index (order on the page) or key.
            For example, ``at.multiselect[0]`` for the first widget or
            ``at.multiselect(key="my_key")`` for a widget with a given key.
        """
        return self._tree.multiselect

    @property
    def number_input(self) -> WidgetList[NumberInput]:
        """Sequence of all ``st.number_input`` widgets.

        Returns
        -------
        WidgetList of NumberInput
            Sequence of all ``st.number_input`` widgets. Individual widgets can
            be accessed from a WidgetList by index (order on the page) or key.
            For example, ``at.number_input[0]`` for the first widget or
            ``at.number_input(key="my_key")`` for a widget with a given key.
        """
        return self._tree.number_input

    @property
    def radio(self) -> WidgetList[Radio[Any]]:
        """Sequence of all ``st.radio`` widgets.

        Returns
        -------
        WidgetList of Radio
            Sequence of all ``st.radio`` widgets. Individual widgets can
            be accessed from a WidgetList by index (order on the page) or key.
            For example, ``at.radio[0]`` for the first widget or
            ``at.radio(key="my_key")`` for a widget with a given key.
        """
        return self._tree.radio

    @property
    def select_slider(self) -> WidgetList[SelectSlider[Any]]:
        """Sequence of all ``st.select_slider`` widgets.

        Returns
        -------
        WidgetList of SelectSlider
            Sequence of all ``st.select_slider`` widgets. Individual widgets can
            be accessed from a WidgetList by index (order on the page) or key.
            For example, ``at.select_slider[0]`` for the first widget or
            ``at.select_slider(key="my_key")`` for a widget with a given key.
        """
        return self._tree.select_slider

    @property
    def selectbox(self) -> WidgetList[Selectbox[Any]]:
        """Sequence of all ``st.selectbox`` widgets.

        Returns
        -------
        WidgetList of Selectbox
            Sequence of all ``st.selectbox`` widgets. Individual widgets can
            be accessed from a WidgetList by index (order on the page) or key.
            For example, ``at.selectbox[0]`` for the first widget or
            ``at.selectbox(key="my_key")`` for a widget with a given key.
        """
        return self._tree.selectbox

    @property
    def slider(self) -> WidgetList[Slider[Any]]:
        """Sequence of all ``st.slider`` widgets.

        Returns
        -------
        WidgetList of Slider
            Sequence of all ``st.slider`` widgets. Individual widgets can
            be accessed from a WidgetList by index (order on the page) or key.
            For example, ``at.slider[0]`` for the first widget or
            ``at.slider(key="my_key")`` for a widget with a given key.
        """
        return self._tree.slider

    @property
    def subheader(self) -> ElementList[Subheader]:
        """Sequence of all ``st.subheader`` elements.

        Returns
        -------
        ElementList of Subheader
            Sequence of all ``st.subheader`` elements. Individual elements can be
            accessed from an ElementList by index (order on the page). For
            example, ``at.subheader[0]`` for the first element. Subheader is an
            extension of the Element class.
        """
        return self._tree.subheader

    @property
    def success(self) -> ElementList[Success]:
        """Sequence of all ``st.success`` elements.

        Returns
        -------
        ElementList of Success
            Sequence of all ``st.success`` elements. Individual elements can be
            accessed from an ElementList by index (order on the page). For
            example, ``at.success[0]`` for the first element. Success is an
            extension of the Element class.
        """
        return self._tree.success

    @property
    def status(self) -> Sequence[Status]:
        """Sequence of all ``st.status`` elements.

        Returns
        -------
        Sequence of Status
            Sequence of all ``st.status`` elements. Individual elements can be
            accessed from a Sequence by index (order on the page). For
            example, ``at.status[0]`` for the first element. Status is an
            extension of the Block class.
        """
        return self._tree.status

    @property
    def table(self) -> ElementList[Table]:
        """Sequence of all ``st.table`` elements.

        Returns
        -------
        ElementList of Table
            Sequence of all ``st.table`` elements. Individual elements can be
            accessed from an ElementList by index (order on the page). For
            example, ``at.table[0]`` for the first element. Table is an
            extension of the Element class.
        """
        return self._tree.table

    @property
    def tabs(self) -> Sequence[Tab]:
        """Sequence of all tabs within ``st.tabs`` elements.

        Each tab within a single ``st.tabs`` will be returned as a separate Tab
        in the Sequence. Additionally, the tab labels are forwarded to each
        Tab element as a property. For example, ``st.tabs("A","B")`` will
        yield two Tab objects, with ``Tab.label`` returning "A" and "B",
        respectively.

        Returns
        -------
        Sequence of Tab
            Sequence of all tabs within ``st.tabs`` elements. Individual
            tabs can be accessed from an ElementList by index (order on the
            page). For example, ``at.tabs[0]`` for the first tab. Tab is an
            extension of the Block class.
        """
        return self._tree.tabs

    @property
    def text(self) -> ElementList[Text]:
        """Sequence of all ``st.text`` elements.

        Returns
        -------
        ElementList of Text
            Sequence of all ``st.text`` elements. Individual elements can be
            accessed from an ElementList by index (order on the page). For
            example, ``at.text[0]`` for the first element. Text is an
            extension of the Element class.
        """
        return self._tree.text

    @property
    def text_area(self) -> WidgetList[TextArea]:
        """Sequence of all ``st.text_area`` widgets.

        Returns
        -------
        WidgetList of TextArea
            Sequence of all ``st.text_area`` widgets. Individual widgets can
            be accessed from a WidgetList by index (order on the page) or key.
            For example, ``at.text_area[0]`` for the first widget or
            ``at.text_area(key="my_key")`` for a widget with a given key.
        """
        return self._tree.text_area

    @property
    def text_input(self) -> WidgetList[TextInput]:
        """Sequence of all ``st.text_input`` widgets.

        Returns
        -------
        WidgetList of TextInput
            Sequence of all ``st.text_input`` widgets. Individual widgets can
            be accessed from a WidgetList by index (order on the page) or key.
            For example, ``at.text_input[0]`` for the first widget or
            ``at.text_input(key="my_key")`` for a widget with a given key.
        """
        return self._tree.text_input

    @property
    def time_input(self) -> WidgetList[TimeInput]:
        """Sequence of all ``st.time_input`` widgets.

        Returns
        -------
        WidgetList of TimeInput
            Sequence of all ``st.time_input`` widgets. Individual widgets can
            be accessed from a WidgetList by index (order on the page) or key.
            For example, ``at.time_input[0]`` for the first widget or
            ``at.time_input(key="my_key")`` for a widget with a given key.
        """
        return self._tree.time_input

    @property
    def title(self) -> ElementList[Title]:
        """Sequence of all ``st.title`` elements.

        Returns
        -------
        ElementList of Title
            Sequence of all ``st.title`` elements. Individual elements can be
            accessed from an ElementList by index (order on the page). For
            example, ``at.title[0]`` for the first element. Title is an
            extension of the Element class.
        """
        return self._tree.title

    @property
    def toast(self) -> ElementList[Toast]:
        """Sequence of all ``st.toast`` elements.

        Returns
        -------
        ElementList of Toast
            Sequence of all ``st.toast`` elements. Individual elements can be
            accessed from an ElementList by index (order on the page). For
            example, ``at.toast[0]`` for the first element. Toast is an
            extension of the Element class.
        """
        return self._tree.toast

    @property
    def toggle(self) -> WidgetList[Toggle]:
        """Sequence of all ``st.toggle`` widgets.

        Returns
        -------
        WidgetList of Toggle
            Sequence of all ``st.toggle`` widgets. Individual widgets can
            be accessed from a WidgetList by index (order on the page) or key.
            For example, ``at.toggle[0]`` for the first widget or
            ``at.toggle(key="my_key")`` for a widget with a given key.
        """
        return self._tree.toggle

    @property
    def warning(self) -> ElementList[Warning]:
        """Sequence of all ``st.warning`` elements.

        Returns
        -------
        ElementList of Warning
            Sequence of all ``st.warning`` elements. Individual elements can be
            accessed from an ElementList by index (order on the page). For
            example, ``at.warning[0]`` for the first element. Warning is an
            extension of the Element class.
        """
        return self._tree.warning

    def __len__(self) -> int:
        return len(self._tree)

    def __iter__(self):
        yield from self._tree

    def __getitem__(self, idx: int) -> Node:
        return self._tree[idx]

    def get(self, element_type: str) -> Sequence[Node]:
        """Get elements or widgets of the specified type.

        This method returns the collection of all elements or widgets of
        the specified type on the current page. Retrieve a specific element by
        using its index (order on page) or key lookup.

        Parameters
        ----------
        element_type: str
            An element attribute of ``AppTest``. For example, "button",
            "caption", or "chat_input".

        Returns
        -------
        Sequence of Elements
            Sequence of elements of the given type. Individual elements can
            be accessed from a Sequence by index (order on the page). When
            getting and ``element_type`` that is a widget, individual widgets
            can be accessed by key. For example, ``at.get("text")[0]`` for the
            first ``st.text`` element or ``at.get("slider")(key="my_key")`` for
            the ``st.slider`` widget with a given key.
        """
        return self._tree.get(element_type)

    def __repr__(self) -> str:
        return repr_(self)
